package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;


/**   全局过滤器
 *     校验是否登录
 *      自定义过滤器 GlobalLoginFilter
 *      默认执行 9大过滤器
 *      10大过滤器    1 2 3 4 5 6 7 8 9 10*/
@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {

    //配置URI路径，使用正则表达式匹配原则
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    //浏览器中令牌的名称
    public static final String TOKEN = "token";

    //浏览器中临时用户的名称
    public static final String USERTEMPID = "userTempId";

    //RedisConfig配置类 创建RedisTemplate 不再是原生Redis客户端
    //格式有问题？ 有问题  K类型与V类型 格式
    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${auth.url}")
    private String[] authUrl;

    public static final String LOGINURL = "http://passport.gmall.com/login.html?originUrl=";

    //全局过滤器执行方法
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //exchange封装了下面两个对象，也就是携带了请求和响应的各种信息
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // URL : http://cart.gmall.com/login.html    Tomcat Servlet Http请求
        // URI : /auth/...
        // path :
        //1、获取当前URI
        String path = request.getURI().getPath();

        //2、内部资源不允许浏览器访问：包含/**/inner/**  登录与未登录都不行
        if (antPathMatcher.match("/**/inner/**",path)){
            //是内部资源 无权访问
            return getVoidMono(response,ResultCodeEnum.PERMISSION);
        }

        //3、判断当前Uri是否必须登录：包含/auth就要登录了；否则不用登录（异步和同步路由不一样）
        String userId = getUserId(request);//缓存中存的k和v都是string类型的
        //3.1：异步  /**/auth/**  走网关，直接连接Redis校验用户是否登录（而不是请求用户微服务，因为缓存的作用就是如此）：未登录——直接返回值 201；登录了——放行
        if (antPathMatcher.match("/**/auth/**",path)){
            //当前Path是需要登录 判断用户是否登录

            if(StringUtils.isEmpty(userId)){
                //未登录
                //弹出小窗口  ：异步 Ajax 不刷新页面 异步
                return getVoidMono(response,ResultCodeEnum.LOGIN_AUTH);
            }
        }
        //3.2：同步 /trade.html /order.html  pay.html  走网关，路由到后面页面微服务
        for (String url : authUrl) {
            //注意判断条件要将 大的放左边，小的放右边
            if (path.indexOf(url)!=-1 && StringUtils.isEmpty(userId)){//不等于-1，表示有相同的路径，有一个相同就要登录
                //需要登录；再判断是否登录了；未登录,需要登录
                //直接转发登录页面  同步  window.location.href 刷新页面 同步
                //重定向
                //状态码
                response.setStatusCode(HttpStatus.SEE_OTHER);
                //URL地址
                try {
                    response.getHeaders().set(HttpHeaders.LOCATION,LOGINURL+ URLEncoder.encode(request.getURI().toString(),"UTF-8"));
                } catch (UnsupportedEncodingException e) {//URLEncoder.encode转义url，防止乱码
                    e.printStackTrace();
                }
                return response.setComplete();//完成之后可以响应了,必须return，否则代码继续会往下走，走到放行

            }
        }


        //4、加入购物车
        //当前是网关，传递用户ID给后面的微服务   请求头 K:userId V:3
        //request.getHeaders().set("userId",userId);
        //mutate改变
        //(1)传递真实的用户Id
        if (!StringUtils.isEmpty(userId)){//防止从首页进入客户端，但是被上面“3”中没有被拦截，但是进入购物车是要登录的情况
            request.mutate().header("userId",userId);//重新发起一个请求给微服务，携带id参数，实现网关和微服务之间的数据传输
        }
        //(2)传递临时的用户Id
        String userTempId = getUserTempId(request);
        if (!StringUtils.isEmpty(userTempId)){
            request.mutate().header("userTempId",userTempId);//重新发起一个请求给微服务，携带id参数，实现网关和微服务之间的数据传输
        }



        //5、放行
        return chain.filter(exchange);//exchange包含request
    }

    private Mono<Void> getVoidMono(ServerHttpResponse response,ResultCodeEnum resultCodeEnum) {
        Result<Object> r = Result.build(null, resultCodeEnum);
        String result = JSONObject.toJSONString(r);
        DataBuffer dataBuffer = response.bufferFactory().wrap(result.getBytes());
        //解决Response乱码问题 Content-Type:application/json;charset=utf-8
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_UTF8_VALUE);
        return response.writeWith(Mono.just(dataBuffer));
    }

    //获取临时的用户Id
    private String getUserTempId(ServerHttpRequest request) {
        //请求头  异步请求时获取方式
        String userTempTd = request.getHeaders().getFirst(USERTEMPID);//如果是异步时；getFirst不是获取第一个，而是获取一个；
        if (StringUtils.isEmpty(userTempTd)){
            //同步请求时 获取方式
            HttpCookie httpCookie = request.getCookies().getFirst(USERTEMPID);//如果是同步时，头是空，就从cooKie中调用
            if (httpCookie != null){
                userTempTd = httpCookie.getValue();//将value值赋过去，而不是key值
            }
        }
        return userTempTd;
    }


    //判断用户是否  获取用户ID
    private String getUserId(ServerHttpRequest request) {
        //页面上的两种请求不同
        //①异步Ajax请求  令牌放在请求头中
        //②同步 请求时 令牌放在了Cookie中
        //1:获取令牌     token其实就是我们传过去的UUID
        String token = request.getHeaders().getFirst(TOKEN);//如果是异步时；getFirst不是获取第一个，而是获取一个；
        if (StringUtils.isEmpty(token)){
            HttpCookie httpCookie = request.getCookies().getFirst(TOKEN);//如果是同步时，头是空，就从cooKie中调用
            if (httpCookie != null){
                token = httpCookie.getValue();//将value值赋过去，而不是key值
            }
        }

        //2:有 去缓存中查询是否存在用户ID   有：登录 没有：未登录
        if (!StringUtils.isEmpty(token)){
            //是否存在
            if(redisTemplate.hasKey(token)){
                return (String) redisTemplate.opsForValue().get(token);
            }
        }

        //3:没有 未登录
        return null;
    }


    //过滤器执行顺序   负整数最大值    0    正整数最大值
    //                 最优先执行    中     最后执行
    @Override
    public int getOrder() {
        return 0;//经过公司实际测试，0是最合适的，不能写2，影响太大
    }
}
